﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Network;
using Microsoft.Xna.Framework;
using GameObjects.DrawableClasses;
using GameObjects;
using GameObjects.ProtoClasses;

namespace GameProcess.Actors
{
    class HitsActor : IActor, IPacketHandler
    {
        GameState _gameState;

        public HitsActor(GameState gameState)
        {
            this._gameState = gameState;
        }

        public void UpdateClient(GameTime gameTime)
        {
            ushort sequence = _gameState.NetPlayer.ClientPacket.Sequence;
            lock (_gameState.NetPlayer.ClientPacket.Shoots)
            {
                _gameState.NetPlayer.ClientPacket.Shoots[sequence] = _gameState.Player.ShootingTime;
                _gameState.Player.ShootingTime = 0;
            }
        }

        public void UpdateServer(GameTime gameTime)
        {
            // ничего не нужно делать
            foreach (var player in _gameState.Players.Values)
            {
                player.Health += (float)(player.HealthRegen * gameTime.ElapsedGameTime.TotalSeconds);
                player.Mana += (float)(player.ManaRegen * gameTime.ElapsedGameTime.TotalSeconds);
                if (player.Health > Character.MaxHealth)
                {
                    player.Health = Character.MaxHealth;
                }
                if (player.Mana > Character.MaxMana)
                {
                    player.Mana = Character.MaxMana;
                }
                // сообщаем всем игрокам состояние маны стреляющего игрока
                foreach (var netPlayer in _gameState.NetPlayers.Values)
                {
                    netPlayer.ServerPacket.PlayersHealths[(byte)player.Id] = (byte)player.Health;
                    netPlayer.ServerPacket.PlayersMana[(byte)player.Id] = (byte)player.Mana;
                }
            }
            foreach (var bot in _gameState.Bots)
            {
                bot.Health += (float)(bot.HealthRegen * gameTime.ElapsedGameTime.TotalSeconds);
                bot.Mana += (float)(bot.ManaRegen * gameTime.ElapsedGameTime.TotalSeconds);
                if (bot.Health > Character.MaxHealth)
                {
                    bot.Health = Character.MaxHealth;
                }
                if (bot.Mana > Character.MaxMana)
                {
                    bot.Mana = Character.MaxMana;
                }
                // сообщаем всем игрокам состояние маны стреляющего игрока
                foreach (var netPlayer in _gameState.NetPlayers.Values)
                {
                    netPlayer.ServerPacket.PlayersHealths[(byte)bot.Id] = (byte)bot.Health;
                    netPlayer.ServerPacket.PlayersMana[(byte)bot.Id] = (byte)bot.Mana;
                }
            }
        }

        public void ReceiveClient(ServerPacket msg)
        {
            // анимация выстрела
            foreach (var player in _gameState.Players.Values)
            {
                player.IsShooting = false;
            }
            lock (msg.ShootingPlayers)
            {
                foreach (var player in msg.ShootingPlayers)
                {
                    _gameState.Players[player].IsShooting = true;
                }
            }
            // удаляем из пакета выстрелы игрока, которые уже были обработаны сервером
            HashSet<ushort> keysToRemove = new HashSet<ushort>();
            foreach (var shootSeq in _gameState.NetPlayer.ClientPacket.Shoots.Keys)
            {
                if (msg.Ack > shootSeq)
                {
                    keysToRemove.Add(shootSeq);
                }
            }
            float deletedValue;
            foreach (var key in keysToRemove)
            {
                _gameState.NetPlayer.ClientPacket.Shoots.TryRemove(key, out deletedValue);
            }
        }

        public void ReceiveServer(ClientPacket msg)
        {
            RealCharacter shooter = _gameState.Players[msg.PlayerId];
            foreach (var shoot in msg.Shoots)
            {
                // игнорируем выстрелы, которые уже были обработаны
                if (_gameState.NetPlayers[msg.PlayerId].ServerPacket.Ack >= shoot.Key)
                {
                    continue;
                }
                float shootingTime = shoot.Value;
                float damage = shootingTime * shooter.DamagePerSecond;
                if (shooter.Mana < damage)
                    break;
                shooter.Mana -= damage;
                // сообщаем всем игрокам состояние маны стреляющего игрока
                foreach (var netPlayer in _gameState.NetPlayers.Values)
                {
                    netPlayer.ServerPacket.PlayersMana[(byte)shooter.Id] = (byte)shooter.Mana;
                }
                if (shooter.IsShooting = (shootingTime != 0))
                {
                    double koef = -Math.Tan(MathHelper.PiOver2 - shooter.Angle); // тангенс угла наклона
                    float wrapedAngle = MathHelper.WrapAngle(MathHelper.PiOver2 - shooter.Angle);
                    // проверяем попадания в объекты
                    Rectangle hitArea = new Rectangle(0, 0, _gameState.Level.Width, _gameState.Level.Height);
                    foreach (var solid in _gameState.Level.Solids)
                    {
                        if (CheckHit(wrapedAngle, koef, shooter, solid))
                        {
                            if (solid.Position.X < shooter.Position.X && solid.Position.X > hitArea.X)
                                hitArea.X = (int)solid.Position.X;
                            if (solid.Position.X > shooter.Position.X && solid.Position.X < hitArea.Width)
                                hitArea.Width = (int)solid.Position.X;
                            if (solid.Position.Y < shooter.Position.Y && solid.Position.Y > hitArea.Y)
                                hitArea.Y = (int)solid.Position.Y;
                            if (solid.Position.Y > shooter.Position.Y && solid.Position.Y < hitArea.Height)
                                hitArea.Height = (int)solid.Position.Y;
                        }
                    }
                    // проверяем попадания в игроков
                    foreach (var player in _gameState.Players.Values)
                    {
                        if (player != shooter)
                        {
                            if (((player.Position.X >= shooter.Position.X && player.Position.X <= hitArea.Width) ||
                                (player.Position.X <= shooter.Position.X && player.Position.X >= hitArea.X))
                                &&
                                ((player.Position.Y >= shooter.Position.Y && player.Position.Y <= hitArea.Height) ||
                                (player.Position.Y <= shooter.Position.Y && player.Position.Y >= hitArea.Y)))
                            {
                                if (CheckHit(wrapedAngle, koef, shooter, player))
                                {
                                    ApplyHit(shooter, player, shootingTime);
                                }
                            }
                        }
                    }
                    foreach (var bot in _gameState.Bots)
                    {
                        if (bot != shooter)
                        {
                            if (((bot.Position.X >= shooter.Position.X && bot.Position.X <= hitArea.Width) ||
                                (bot.Position.X <= shooter.Position.X && bot.Position.X >= hitArea.X))
                                &&
                                ((bot.Position.Y >= shooter.Position.Y && bot.Position.Y <= hitArea.Height) ||
                                (bot.Position.Y <= shooter.Position.Y && bot.Position.Y >= hitArea.Y)))
                            {
                                if (CheckHit(wrapedAngle, koef, shooter, bot))
                                {
                                    ApplyHit(shooter, bot, shootingTime);
                                }
                            }
                        }
                    }
                }
            }
            // сообщаем всем игрокам, что игрок стреляет
            foreach (var netPlayer in _gameState.NetPlayers.Values)
            {
                lock (netPlayer.ServerPacket.ShootingPlayers)
                {
                    if (shooter.IsShooting)
                    {
                        netPlayer.ServerPacket.ShootingPlayers.Add(msg.PlayerId);
                    }
                    else
                    {
                        netPlayer.ServerPacket.ShootingPlayers.Remove(msg.PlayerId);
                    }
                }
            }
        }

        public bool CheckHit(float angle, double koef, RealCharacter shooter, ISolid target)
        {
            double b1 = shooter.Position.X;
            double b2 = shooter.Position.Y;
            // пересечение с вертикальными прямыми
            float playerLeft = target.Position.X - (target.CollisionOffset.X >> 1);
            float playerRight = target.Position.X + (target.CollisionOffset.X >> 1);
            float playerTop = target.Position.Y - (target.CollisionOffset.Y >> 1);
            float playerBottom = target.Position.Y + (target.CollisionOffset.Y >> 1);
            if ((angle < MathHelper.PiOver2) && (angle > -MathHelper.PiOver2) && (playerLeft > shooter.Position.X))
            {
                double y = koef * (playerLeft - b1) + b2;
                if (y > playerTop && y < playerBottom)
                    return true;
            }
            if (((angle > MathHelper.PiOver2) || (angle < -MathHelper.PiOver2)) && (playerRight < shooter.Position.X))
            {
                double y = koef * (playerRight - b1) + b2;
                if (y > playerTop && y < playerBottom)
                    return true;
            }
            // пересечение с горизонтальными прямыми
            if ((angle > 0) && (angle < MathHelper.Pi) && (playerBottom < shooter.Position.Y))
            {
                double x = 1.0 / koef * (playerBottom - b2) + b1;
                if (x > playerLeft && x < playerRight)
                    return true;
            }
            if ((angle < 0) && (angle > -MathHelper.Pi) && (playerTop > shooter.Position.Y))
            {
                double x = 1.0 / koef * (playerTop - b2) + b1;
                if (x > playerLeft && x < playerRight)
                    return true;
            }
            return false;
        }

        private void ApplyHit(RealCharacter shooter, RealCharacter player, float shootingTime)
        {
            player.Health -= shootingTime * shooter.DamagePerSecond;
            if (player.Health <= 0)
            {
                player.Position = _gameState.Level.GetRespawnPosition();
                player.Health = Character.MaxHealth;
                _gameState.Scores[shooter.Id].Kills++;
                _gameState.Scores[player.Id].Deaths++;
                foreach (var netPlayer in _gameState.NetPlayers.Values)
                {
                    netPlayer.ServerPacket.PlayersPositions[player.Id] = (ProtoVector2)player.Position;
                    netPlayer.ServerPacket.PlayersHealths[(byte)player.Id] = (byte)player.Health;
                }
            }
            else
            {
                foreach (var netPlayer in _gameState.NetPlayers.Values)
                {
                    netPlayer.ServerPacket.PlayersHealths[(byte)player.Id] = (byte)player.Health;
                }
            }
        }
    }
}
